package com.chagee.channel.server.event.service.impl;


import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpStatus;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.json.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.chagee.channel.api.bo.lark.*;
import com.chagee.channel.common.constant.LarkConstant;
import com.chagee.channel.common.exception.BizErrorCode;
import com.chagee.channel.common.exception.BizException;
import com.chagee.channel.server.config.LarkConfig;
import com.chagee.channel.server.event.model.LarkDecryptBO;
import com.chagee.channel.server.event.service.LarkService;
import com.chagee.channel.server.event.service.strategy.larkCallback.CallbackHandler;
import com.chagee.channel.server.event.service.strategy.larkCallback.CallbackHandlerFactory;
import java.security.MessageDigest;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.*;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;

import com.chagee.channel.server.util.HttpUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.ContentType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

/**
 * 对接飞书接口
 *
 * @author: chang.jiawei
 * @date: 2024/01/18
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class LarkServiceImpl implements LarkService {

    private final LarkConfig larkConfig;

    private final CallbackHandlerFactory callbackHandlerFactory;

    /**
     * 获取tenantAccessToken
     *
     * @return tenantAccessToken
     */
    @SneakyThrows
    public String getTenantAccessToken() {
        HttpResponse response = HttpRequest.post(larkConfig.getOpenAPI().getTenantAccessToken())
            .form(LarkConstant.APP_ID_KEY, larkConfig.getApplication().getAppId())
            .form(LarkConstant.APP_SECRET_KEY, larkConfig.getApplication().getAppSecret())
            .execute();
        checkHttpStatus(response, larkConfig.getOpenAPI().getTenantAccessToken());
        return (String) JSONObject.parseObject(response.body()).get(LarkConstant.TENANT_ACCESS_TOKEN);
    }

    @SneakyThrows
    @Override
    public FileUploadResultBO upload2Lark(UploadFileBO uploadFile) {
        log.info("开始上传文件,计时开始,{},", uploadFile.getFileName());
        Instant start = Instant.now();
        HttpResponse response = HttpRequest.post(larkConfig.getOpenAPI().getUploadFile())
            .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
            .contentType(ContentType.MULTIPART_FORM_DATA.getMimeType())
            .form(LarkConstant.FILE_PARAM_KEY, uploadFile.getFile(), uploadFile.getFileName())
            .form(LarkConstant.FILE_NAME_PARAM_KEY, uploadFile.getFileName())
            .form(LarkConstant.FILE_TYPE_PARAM_KEY, uploadFile.getFileType().getValue())
            .execute();
        Instant end = Instant.now();
        long duration = Duration.between(start, end).toMillis();
        log.info("上传文件结束,计时,{}ms,", duration);
        log.info("上传文件返回：{}", response);
        checkHttpStatus(response, larkConfig.getOpenAPI().getUploadFile());
        JSONObject responseData = JSONObject.parseObject(response.body()).getJSONObject(LarkConstant.RESPONSE_DATA);
        return new FileUploadResultBO().setCode(responseData.getString(LarkConstant.FILE_CODE_KEY)).setUrl(responseData.getString(LarkConstant.FILE_URL_KEY));
    }

    @SneakyThrows
    @Override
    public String startApprove(StartApprovalBO startApprovalBO) {
        HttpResponse response = HttpRequest.post(larkConfig.getOpenAPI().getStartApproval())
            .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
            .contentType(ContentType.APPLICATION_JSON.getMimeType())
            .body(JSONObject.toJSONString(startApprovalBO))
            .execute();
        checkHttpStatus(response, larkConfig.getOpenAPI().getStartApproval());
        return JSONObject.parseObject(response.body()).getJSONObject(LarkConstant.RESPONSE_DATA).getString(LarkConstant.INSTANCE_CODE_KEY);
    }

    @SneakyThrows
    @Override
    public void subscribeApproval(String code) {
        String url = StrUtil.format(larkConfig.getOpenAPI().getSubscribeApproval(), code);
        HttpResponse response = HttpRequest.post(url)
            .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
            .contentType(ContentType.APPLICATION_JSON.getMimeType())
            .execute();
        checkHttpStatus(response, url);
        log.info("飞书审批订阅成功code:{}", code);
    }

    @Override
    public void unsubscribeApproval(String code) {
        String url = StrUtil.format(larkConfig.getOpenAPI().getUnsubscribeApproval(), code);
        HttpResponse response = HttpRequest.post(url)
            .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
            .execute();
        checkHttpStatus(response, url);
        log.info("飞书审批取消订阅code:{}", code);
    }

    @SneakyThrows
    @Override
    public boolean checkSignature(JSONObject body, HttpServletRequest request) {
        String timeStamp = request.getHeader(LarkConstant.TIMESTAMP_HEADER);
        String nonce = request.getHeader(LarkConstant.NONCE_HEADER);
        String signature = request.getHeader(LarkConstant.SIGNATURE_HEADER);
        MessageDigest alg = MessageDigest.getInstance(LarkConstant.MESSAGE_DIGEST);
        String sign = Hex.encodeHexString(alg.digest((timeStamp + nonce + larkConfig.getEvent().getEncryptKey() + body).getBytes()));
        if (!StringUtils.equals(sign, signature)) {
            log.error("timeStamp:{}; nonce:{}; signature:{}; request:{}", timeStamp, nonce, signature, request);
            log.error("飞书请求校验未通过！sign: {}; signature:{}", sign, signature);
            return false;
        }
        return true;
    }

    @SneakyThrows
    @Override
    public JSONObject decrypt(String encrypt) {
        LarkDecryptBO larkDecryptBO = new LarkDecryptBO(larkConfig.getEvent().getEncryptKey());
        String result = larkDecryptBO.decrypt(encrypt);
        log.info("decryptResult:{}", result);
        return JSONObject.parseObject(result);
    }

    @Override
    public JSONObject handleCallback(JSONObject param) {
        log.info("lark callback json body: {}", param);
        if (!param.containsKey(LarkConstant.EVENT_KEY)) {
            return new JSONObject();
        }
        JSONObject eventBody = param.getJSONObject(LarkConstant.EVENT_KEY);
        CallbackHandler handler = callbackHandlerFactory.getCallbackHandler(eventBody.getString("type"));
        if (Objects.isNull(handler)) {
            return new JSONObject();
        }
        handler.process(eventBody);
        return new JSONObject();
    }

    private void checkHttpStatus(HttpResponse response, String url) {
        if (HttpStatus.HTTP_OK != response.getStatus() || JSONObject.parseObject(response.body()).getLongValue("code") != 0L) {
            log.error("调用接口失败！http url:{}, response status:{}, headers:{}, body:{}",
                    url, response.getStatus(), JSONUtil.toJsonStr(response.headers()), response.body());
            throw new BizException(BizErrorCode.FAIL);
        }
    }

    @Override
    public void subscribeAllApproval() {
        for (Map<String, String> codeMap : larkConfig.getApprovalCode().values()) {
            Collection<String> codes = codeMap.values();
            for (String code : codes) {
                try {
//                    Thread.sleep(2000);
                    subscribeApproval(code);
                } catch (Exception e) {
                    //Do Nothing
                }
            }
        }
    }

    @Override
    public ApprovalInstanceDetailBO approvalInstanceDetail(String instanceCode) {
        String url = StrUtil.format(larkConfig.getOpenAPI().getApprovalInstanceDetail(), instanceCode);
        StopWatch stopWatch = new StopWatch("approvalInstanceDetail");
        stopWatch.start();
        log.info("请求飞书url: {}", url);
        HttpResponse response = HttpRequest.get(url)
            .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
            .execute();
        stopWatch.stop();
        log.info("StopWatch '" + stopWatch.getId() + "': running time = " + stopWatch.getTotalTimeMillis() + " ms");
        checkHttpStatus(response, url);
        return JSONObject.parseObject(response.body()).getObject(LarkConstant.RESPONSE_DATA, ApprovalInstanceDetailBO.class);
    }

    @Override
    public void revokeApprove(RevokeApprovalBO revokeApprovalBO) {
        HttpResponse response = HttpRequest.post(larkConfig.getOpenAPI().getRevokeApproval())
            .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
            .contentType(ContentType.APPLICATION_JSON.getMimeType())
            .body(JSONObject.toJSONString(revokeApprovalBO))
            .execute();
        checkHttpStatus(response, larkConfig.getOpenAPI().getRevokeApproval());
    }

    @Override
    public void approveApprove(ApproveApprovalBO approveApprovalBO) {
        HttpResponse response = HttpRequest.post(larkConfig.getOpenAPI().getApproveApproval())
            .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
            .contentType(ContentType.APPLICATION_JSON.getMimeType())
            .body(JSONObject.toJSONString(approveApprovalBO))
            .execute();
        checkHttpStatus(response, larkConfig.getOpenAPI().getApproveApproval());
    }

    @Override
    public List<ApprovalPreviewNodeBO> approvalInstancePreview(PreviewApprovalBO previewApprovalBO) {
        HttpResponse response = HttpRequest.post(larkConfig.getOpenAPI().getApprovalInstancePreview())
            .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
            .contentType(ContentType.APPLICATION_JSON.getMimeType())
            .body(JSONObject.toJSONString(previewApprovalBO))
            .execute();
        checkHttpStatus(response, larkConfig.getOpenAPI().getStartApproval());
        return JSONObject.parseArray(JSONObject.parseObject(response.body()).getJSONObject(LarkConstant.RESPONSE_DATA).getJSONArray(LarkConstant.PREVIEW_NODES_KEY).toJSONString(),
            ApprovalPreviewNodeBO.class);
    }

    @Override
    public ApprovalDetailBO approvalDetail(String approvalCode) {
        String url = StrUtil.format(larkConfig.getOpenAPI().getApprovalDetail(), approvalCode);
        HttpResponse response = HttpRequest.get(url)
            .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
            .execute();
        checkHttpStatus(response, larkConfig.getOpenAPI().getApprovalDetail());
        return JSONObject.parseObject(response.body()).getObject(LarkConstant.RESPONSE_DATA, ApprovalDetailBO.class);
    }

    @Override
    public void rejectApprove(RejectApprovalBO rejectApprovalBO) {
        HttpResponse response = HttpRequest.post(larkConfig.getOpenAPI().getRejectApproval())
            .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
            .contentType(ContentType.APPLICATION_JSON.getMimeType())
            .body(JSONObject.toJSONString(rejectApprovalBO))
            .execute();
        checkHttpStatus(response, larkConfig.getOpenAPI().getRejectApproval());
    }

    @Override
    public ApprovalCommentListResponse approvalCommentsList(ApprovalCommentListRequest request) {
        String url = StrUtil.format(larkConfig.getOpenAPI().getApprovalCommentList(), request.getInstanceCode());
        HttpResponse response = HttpRequest.get(url)
            .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
            .form(LarkConstant.USER_ID, request.getUserId())
            .form(LarkConstant.PAGE_TOKEN, request.getPageToken())
            .form(LarkConstant.PAGE_SIZE, request.getPageSize())
            .execute();
        checkHttpStatus(response, larkConfig.getOpenAPI().getRejectApproval());
        return JSONObject.parseObject(response.body()).getObject(LarkConstant.RESPONSE_DATA, ApprovalCommentListResponse.class);
    }

    @SneakyThrows
    @Override
    public FileUploadResultBO uploadFileByUrl(UploadFileUrlBO uploadFile) {
        String finalUrl = uploadFile.getUrl().replaceAll("\u0026", "&");
        byte[] pic = HttpUtils.downloadBytes(finalUrl);
        UploadFileBO param = UploadFileBO.transfer(uploadFile, pic);

        return this.upload2Lark(param);
    }

    @Override
    public ApprovalCarbonCopyResponse approvalCarbonCopy(ApprovalCarbonCopyRequest request) {
        HttpResponse response = HttpRequest.post(larkConfig.getOpenAPI().getApprovalCarbonCopy())
                .header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
                .contentType(ContentType.APPLICATION_JSON.getMimeType())
                .body(JSONObject.toJSONString(request))
                .execute();
        checkHttpStatus(response, larkConfig.getOpenAPI().getApprovalCarbonCopy());
        return new ApprovalCarbonCopyResponse(true);
    }

	/**
	 * 审批实例详情-全文评论
	 *
	 * @param instanceCode
	 * @param userId
	 * @return
	 */
	@Override
	public ApprovalCommentsBO approvalInstanceComment(String instanceCode, String userId) {
		String commentUrl = StrUtil.format(larkConfig.getOpenAPI().getApprovalInstanceComment(), instanceCode, userId);
		HttpResponse commentResponse = HttpRequest.get(commentUrl)
			.header(LarkConstant.AUTHORIZATION_HEADER_KEY, LarkConstant.AUTHORIZATION_HEADER_VALUE_PREFIX + getTenantAccessToken())
			.execute();
		checkHttpStatus(commentResponse, larkConfig.getOpenAPI().getApprovalInstanceComment());
		ApprovalCommentsBO approvalCommentsBO = JSONObject.parseObject(commentResponse.body()).getObject(LarkConstant.RESPONSE_DATA, ApprovalCommentsBO.class);
		return this.transferComment(approvalCommentsBO);
	}

	/**
	 * 评论信息数据结构转换
	 *
	 * @param approvalCommentsBO
	 * @return
	 */
	private ApprovalCommentsBO transferComment(ApprovalCommentsBO approvalCommentsBO) {
		List<ApprovalCommentBO> comments = approvalCommentsBO.getComments();
		if (CollectionUtils.isNotEmpty(comments)) {
			// 遍历并处理评论
			List<ApprovalCommentBO> approvalCommentBOList = comments.stream().map(approvalCommentBO -> {
				List<ApprovalInstanceDetailBO.FileVO> fileVOList = new ArrayList<>();
				String comment = this.commentHandler(approvalCommentBO.getContent(), fileVOList);
				approvalCommentBO.setContent(comment);
				approvalCommentBO.setFiles(fileVOList);

				List<ApprovalCommentBO.CommentReplyVO> replies = approvalCommentBO.getReplies();
				if (CollectionUtils.isNotEmpty(replies)) {
					// 遍历并处理评论的回复
					List<ApprovalCommentBO.CommentReplyVO> commentReplyVOList = replies.stream().map(reply -> {
						List<ApprovalInstanceDetailBO.FileVO> repliesFileVOList = new ArrayList<>();
						String repliesComment = this.commentHandler(reply.getContent(), repliesFileVOList);
						reply.setContent(repliesComment);
						reply.setFiles(repliesFileVOList);
						return reply;
					}).collect(Collectors.toList());

					approvalCommentBO.setReplies(commentReplyVOList);
				}

				return approvalCommentBO;
			}).collect(Collectors.toList());

			approvalCommentsBO.setComments(approvalCommentBOList);
		}
		return approvalCommentsBO;
	}

	/**
	 * 处理评论信息和附件
	 *
	 * @param comment
	 * @param fileVOList
	 * @return
	 */
	private String commentHandler(String comment, List<ApprovalInstanceDetailBO.FileVO> fileVOList) {
		try {
			cn.hutool.json.JSONObject jsonObject = new cn.hutool.json.JSONObject(comment);
			JSONArray files = jsonObject.getJSONArray(LarkConstant.COMMENT_FILES);

			for (int i = 0; i < files.size(); i++) {
				cn.hutool.json.JSONObject file = files.getJSONObject(i);
				String title = file.getStr(LarkConstant.COMMENT_TITLE);
				String url = file.getStr(LarkConstant.COMMENT_URL);
				String type = file.getStr(LarkConstant.COMMENT_TYPE);
				long fileSize = file.getLong(LarkConstant.COMMENT_FILESIZE);
				if (StrUtil.isNotBlank(url)) {
					ApprovalInstanceDetailBO.FileVO fileVO = new ApprovalInstanceDetailBO.FileVO();
					fileVO.setFileSize(fileSize);
					fileVO.setType(type);
					fileVO.setTitle(title);
					fileVO.setUrl(url);
					fileVOList.add(fileVO);
				}
			}

			return jsonObject.getStr(LarkConstant.COMMENT_TEXT);
		} catch (Exception e) {
			log.error("com.chagee.channel.server.event.service.impl.LarkServiceImpl.commentHandler:", e);
		}
		return BizErrorCode.FAIL.getErrorMsg();
	}
}
